﻿/*
 * PathNormal.cs
 *
 * Created on November 17, 2007, 4:38 PM
 *
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 * Copyright (C) 2010       Mathijs Miermans
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

using HexTD.util;

namespace HexTD.wave
{
	public class PathNormal : Path
	{

		private Context context;
		private List<int> stepXv = new List<int>();
		private List<int> stepYv = new List<int>();
		private int[] stepsX;
		private int[] stepsY;
		private Color blipColor = Color.FromArgb(255, 180, 180, 255);
		private Color blipBackColor = Color.FromArgb(0, 180, 180, 255);
		private Brush stroke;
		private Shape blipShape;
		private Brush blipFill;
		private float blipSize;
		private Image[] blipImage;
		private int[,] blipStep;
		private int blipStepDelta = 2;
		RotateTransform blipTranslate = new RotateTransform();
		private int stepsPerSegment = 2;
		private int timePerStep = 40;
		private int localTime = 0;
		private int stepTime = 0;
		private bool selected = false;
		private float baseDelay;

		/**
		 * Creates a new instance of PathNormal
		 */
		public PathNormal(Context context, float baseDelay)
		{
			this.context = context;
			this.baseDelay = baseDelay;
		}

		public float getBaseDelay()
		{
			return this.baseDelay;
		}

		public void setBaseDelay(float d)
		{
			this.baseDelay = d;
		}

		public void addStep(int x, int y)
		{
			this.stepXv.Add(x);
			this.stepYv.Add(y);
		}

		public int length()
		{
			return this.stepsX.Length;
		}

		public int[] getStepHexCoords(int step)
		{
			int[] retval = new int[2];
			retval[0] = this.stepsX[step];
			retval[1] = this.stepsY[step];
			return retval;
		}

		public float[] getStepPaint(int step)
		{
			float[] loc = this.getStep(step);
			loc[0] *= this.context.paintScale;
			loc[1] *= this.context.paintScale;
			loc[0] += this.context.offsetX;
			loc[1] += this.context.offsetY;
			return loc;
		}

		public float[] getStep(int step)
		{
			if (this.stepsX.Length == 0)
			{
				float[] retval = { 0f, 0f };
				return retval;
			}
			else
			{
				if (step < 0)
				{
					return this.getPreStep();
				}
				if (step >= this.stepsX.Length)
				{
					return this.getPostStep();
				}
				float[] returnVals = new float[2];
				int x = stepsX[step];
				int y = stepsY[step];
				if (x < 0 || y < 0 || x >= context.gridWidth || y >= context.gridHeight || context.grid[x, y] == null)
				{
					// TODO some maps need a delay for timing. This is now done by adding invalid points at the start.
					// Until proper delay code is added, this warning is disabled.
					//System.Diagnostics.Debug.WriteLine("ERROR, nonexistant point requested: "+x+","+y);
					float[] retval = { 0f, 0f };
					return retval;
				}
				return context.grid[x, y].getFloatLocation();
			}
		}

		private float[] getPreStep()
		{
			int dx = 0;
			int dy = 0;
			float[] retVal = new float[2];
			if (this.stepsX[0] == 0)
			{
				dx = -2;
			}
			if (this.stepsX[0] == this.context.gridWidth - 1 - (this.stepsY[0] & 1))
			{
				dx = 2;
			}
			if (this.stepsY[0] == 0)
			{
				dy = -2;
			}
			if (this.stepsY[0] == this.context.gridHeight - 1)
			{
				dy = 2;
			}
			retVal = this.getStep(0);
			retVal[0] += dx;
			retVal[1] += dy;
			return retVal;
		}

		private float[] getPostStep()
		{
			int count = this.stepsX.Length - 1;
			int dx = 0;
			int dy = 0;
			float[] retVal = new float[2];
			if (this.stepsX[count] == 0)
			{
				dx = -2;
			}
			if (this.stepsX[count] == this.context.gridWidth - 1 - (this.stepsY[count] & 1))
			{
				dx = 2;
			}
			if (this.stepsY[count] == 0)
			{
				dy = -2;
			}
			if (this.stepsY[count] == this.context.gridHeight - 1)
			{
				dy = 2;
			}
			retVal = this.getStep(count);
			retVal[0] += dx;
			retVal[1] += dy;
			return retVal;
		}

		public void insertStep(int i, int x, int y)
		{
			this.stepXv.Insert(i, x);
			this.stepYv.Insert(i, y);
			this.finalise();
		}

		public void updateStep(int i, int x, int y)
		{
			this.stepXv.Remove(i);
			this.stepYv.Remove(i);
			this.stepXv.Insert(i, x);
			this.stepYv.Insert(i, y);
			this.finalise();
		}

		public void removeStep(int i)
		{
			this.stepXv.Remove(i);
			this.stepYv.Remove(i);
			this.finalise();
		}

		public void finalise()
		{
			this.stepsX = this.stepXv.ToArray();
			this.stepsY = this.stepYv.ToArray();

			int blipCount = 1 + this.length() / this.blipStepDelta;
			this.blipStep = new int[blipCount, 4];

			for (int i = 0; i < this.blipStep.GetLength(0); i++)
			{
				this.blipStep[i, 0] = (int)Math.Round(1.0f * i * this.blipStepDelta);
				this.blipStep[i, 1] = 0;
				this.blipStep[i, 2] = this.timePerStep - 1 - (i % this.timePerStep);
				this.blipStep[i, 3] = 0;
			}

		}

		public void paint(Canvas g2, int gameTime)
		{
			if (this.stepsX.Length == 0)
			{
				// Don't try to paint an empty path...
			}
			else
			{
				float paintScale = this.context.paintScale;

				if (this.context.gameState == HexTD.gameState.editing && this.selected)
				{
					float[] point = this.getStepPaint(-1);
					for (int index = 0; index <= this.stepsX.Length; index++)
					{
						point = this.getStepPaint(index);
#if PORT
						poly1.lineTo(point[0], point[1]);
						g2.setStroke(this.stroke);
#endif
						EllipseGeometry circle = new EllipseGeometry();
						circle.Center = new Point(point[0], point[1]);
						circle.RadiusX = this.blipSize;
						circle.RadiusY = this.blipSize;
						System.Windows.Shapes.Path circlePath = new System.Windows.Shapes.Path();
						circlePath.Fill = new SolidColorBrush(this.blipColor);
						g2.Children.Add(circlePath);
					}
#if PORT
					g2.draw(poly1);
#endif

				}
#if PORT
				else
				{

					int correction = 0;
					if (this.blipStep[1, 0] > blipStepDelta && this.blipStep[1, 3] == 0)
					{
						correction = blipStepDelta;
						for (int i = this.blipStep.GetLength(0) - 1; i >= 0; i--)
						{
							this.blipStep[i, 3] -= correction;
						}
					}

					for (int i = 0; i < this.blipStep.GetLength(0); i++)
					{
						this.blipStep[i, 2]++;
						if (this.blipStep[i, 2] == this.blipImage.Length)
						{
							this.blipStep[i, 2] = 0;
							this.blipStep[i, 0] += this.blipStep[i, 3];
							this.blipStep[i, 3] = 0;
							this.blipStep[i, 1]++;
							if (this.blipStep[i, 1] >= this.stepsPerSegment)
							{
								this.blipStep[i, 1] = 0;
								this.blipStep[i, 0] += 1;
								this.blipStep[i, 3] = 0;
							}
						}
						float[] point1 = this.getStepPaint(this.blipStep[i, 0]);
						float[] point2 = this.getStepPaint(this.blipStep[i, 0] + 1);
						float x, y;
						int image = this.blipStep[i, 2];
						x = point1[0] - this.blipSize / 2 + (point2[0] - point1[0]) * this.blipStep[i, 1] / this.stepsPerSegment;
						y = point1[1] - this.blipSize / 2 + (point2[1] - point1[1]) * this.blipStep[i, 1] / this.stepsPerSegment;
						g2.drawImage(this.blipImage[image], (int)x, (int)y, null);
					}
					g2.setStroke(defaultStroke);
				}
#endif
			}
		}

		public void resetScale()
		{
			if (this.blipImage != null)
			{
#if PORT_REM
				for (int i = 0; i < this.blipImage.Length; i++)
				{
					if (this.blipImage[i] != null)
					{
						this.blipImage[i].flush();
					}
				}
#endif
			}
			else
			{
				this.blipImage = new Image[this.timePerStep];
			}

#if PORT
			this.stroke = new Brush(1.0f * this.context.strokeScale, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL);

			this.blipSize = this.context.paintScale / 4f;
			this.blipShape = new Ellipse2D.Float(0, 0, this.blipSize, this.blipSize);
			float[] dist = { 0.0f, 1f };
			int num = this.blipImage.Length;
			for (int i = 0; i < num; i++)
			{
				int trans = Math.Max(0, 255 - i * 500 / num);
				Color[] colors = { Color.FromArgb(this.blipBackColor, this.blipColor.getRed(), this.blipColor.getGreen(), this.blipColor.getBlue(), trans) };
				this.blipFill = new RadialGradientPaint(this.blipSize / 2, this.blipSize / 2, this.blipSize / 2.1f, dist, colors);

				this.blipImage[i] = new BufferedImage(Math.Round(this.blipSize), Math.Round(this.blipSize), BufferedImage.TYPE_INT_ARGB_PRE);
				Canvas g2 = this.blipImage[i].createGraphics();
				g2.setPaint(blipFill);
				g2.fill(blipShape);
				g2.dispose();
			}
#endif
		}

		private void shiftUpDown(int to, int correction)
		{
			int x, y, shift;
			for (int i = 0; i < this.stepsY.Length; i++)
			{
				//this.stepsY[i] += to;
				y = this.stepYv[i];
				x = this.stepXv[i];
				this.stepYv.Remove(i);
				this.stepXv.Remove(i);

				shift = 0;
				bool odd = ((y & 1) == 1);
				if (correction < 0)
				{
					odd = !odd;
				}
				if (odd)
				{
					shift = correction;
				}

				y += to;
				x += shift;
				this.stepYv.Insert(i, y);
				this.stepXv.Insert(i, x);

			}
			this.finalise();
		}

		private void shiftLeftRight(int to)
		{
			int y;
			for (int i = 0; i < this.stepsX.Length; i++)
			{
				//this.stepsX[i] += to;
				y = this.stepXv[i];
				this.stepXv.Remove(i);
				y += to;
				this.stepXv.Insert(i, y);
			}
			this.finalise();
		}

		public void shift(HexTD.direction direction, int amount, int correction)
		{
			switch (direction)
			{
				case HexTD.direction.UP:
				case HexTD.direction.NORTH:
					this.shiftUpDown(-amount, correction);
					break;
				case HexTD.direction.DOWN:
				case HexTD.direction.SOUTH:
					this.shiftUpDown(amount, correction);
					break;
				case HexTD.direction.RIGHT:
				case HexTD.direction.EAST:
					this.shiftLeftRight(amount);
					break;
				case HexTD.direction.LEFT:
				case HexTD.direction.WEST:
					this.shiftLeftRight(-amount);
					break;
			}
		}

		public bool isSelected()
		{
			return selected;
		}

		public void setSelected(bool selected)
		{
			this.selected = selected;
		}
	}
}
